package nina.yiyou.attendance.s10merge.compute

import nina.yiyou.attendance.s10merge.compute.latepoint.LatePointCalculatorFactory
import nina.yiyou.attendance.s10merge.compute.offgain.OffGainCalculatorFactory
import nina.yiyou.attendance.s10merge.compute.wage.IsProbationChecker
import nina.yiyou.attendance.s10merge.compute.wage.WageHourCalculator
import nina.yiyou.attendance.s10merge.input.AttendLeaveExcelReader
import nina.yiyou.attendance.s10merge.input.AttendPunchExcelV2Reader
import nina.yiyou.salary.input.config.WorkTimeSheetReader
import nina.yiyou.salary.output.util.KeyPrefixPrepender
import nina.yiyou.salary.process.global.GlobalResultMaker

import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.format.DateTimeFormatter
import java.util.function.Function
import java.util.stream.Stream

import static java.util.stream.Collectors.groupingBy
import static java.util.stream.Collectors.toList

class AttendDetailMerger {

  interface DayResult {

    PunchDay dayBasic()

    String employeeId()

    Map<String, ?> toMap()
  }

  interface PunchDay {

    Employee employee()

    LocalDate date()

    LocalDateTime punchIn()

    LocalDateTime punchOut()

    double workingHour()

    boolean isPunchFree()

    int forgotPunch()

    LocalTime overrideLatePoint()

    List<AttendLeaveExcelReader.Line> leaveList()
  }

  interface Employee {

    String name()

    String department()

    Map<String, Object> toRawMap()
  }

  AttendDetailMerger(List<AttendPunchExcelV2Reader.Line> punchList, Map<String, List<AttendLeaveExcelReader.Line>> leaveMap, Map<String, Map> basicMap, List<WorkTimeSheetReader.Line> workTimeCfg, GlobalResultMaker.Result globalCfg) {
    _punchList = punchList
    _leaveMap = leaveMap
    _basicMap = basicMap
    _workTimeCfg = workTimeCfg
    _globalCfg = globalCfg
  }

  List<DayResult> merge() {
    List<PunchDay> dayList = groupBy(_punchList, { it.name() }).stream()
        .flatMap { nameToDayListV2(it) }
        .collect(toList())

    Map<String, String> idMap = _basicMap.entrySet().stream()
        .map { [it.value['姓名'], it.key] }
        .collect(toList())
        .collectEntries()

    Set<String> ignoreSet = _globalCfg.attendIgnore()
    return dayList.parallelStream()
        .filter { !ignoreSet.contains(it.employee().name()) }
        .map { makeDayResult(it, dayList, idMap) }
        .collect(toList())
  }

  private Collection<List> groupBy(List list, Function<AttendPunchExcelV2Reader.Line, ?> classifier) {
    def map = list.stream().collect(
        groupingBy(classifier, { new LinkedHashMap<>() }, toList())) as Map

    return map.values()
  }

  private Stream<PunchDay> nameToDayListV2(List<AttendPunchExcelV2Reader.Line> samePerson) {
    return samePerson.stream().map { mergeImplV2(it) }
  }

  private PunchDay mergeImplV2(AttendPunchExcelV2Reader.Line dayLine) {
    def employee = new Employee() {
      @Override
      String name() {
        return dayLine.name()
      }

      @Override
      String department() {
        return dayLine.department()
      }

      @Override
      Map<String, Object> toRawMap() {
        return dayLine.toMap()
      }
    }

    return new PunchDayMaker(dayLine.date(), employee, dayLine, dayLine.extraArg(), _leaveMap).make()
  }

  private DayResult makeDayResult(PunchDay day, List<PunchDay> dayList, Map<String, String> nameIdMap) {
    String employeeName = day.employee().name()
    String employeeId = nameIdMap[employeeName] ?: employeeName
    assert employeeId, "找不到工号：${employeeName}"

    Map<String, Object> resultMap = toDayMap(day, dayList, employeeId)
    return new DayResult() {
      @Override
      PunchDay dayBasic() {
        return day
      }

      @Override
      String employeeId() {
        return employeeId
      }

      @Override
      Map<String, ?> toMap() {
        return resultMap
      }
    }
  }

  private Map combineMaps(List inputs) {
    return inputs.collect { String p, Map m -> new KeyPrefixPrepender(m, p).prepend() }
        .inject { sum, val -> sum + val }
  }

  /**
   * @see nina.yiyou.attendance.s10merge.output.MergeResultGenerator#makeTemplateKeys
   */
  private Map toDayMap(PunchDay day, List<PunchDay> dayList, String employeeId) {
    LocalDateTime punchIn = day.punchIn()
    LocalDateTime latePoint = new LatePointCalculatorFactory(day, dayList, _workTimeCfg).create().calc()
    def timeFmt = { LocalDateTime t -> t ? t.format(FORMAT) : null }

    LocalDate date = day.date()
    LocalDateTime punchOut = day.punchOut()

    boolean isProba = new IsProbationChecker(employeeId, date, _basicMap).check()
    double probaHour = isProba ? 7.5 : 0
    double fullHour = 7.5 - probaHour

    List<AttendLeaveExcelReader.Line> leaveList = day.leaveList()
    double wageHour = new WageHourCalculator(probaHour + fullHour, leaveList).calc()

    Map computeMap = [
        '上班'    : punchIn?.toLocalTime(),
        '下班'    : timeFmt(punchOut),
        '时长'    : day.workingHour(),
        '迟到线'  : day.isPunchFree() ? null : latePoint.toLocalTime(),
        '迟到分钟': new LateMinuteCalculator(day, punchIn, latePoint).calc(),
        '获得调休': new OffGainCalculatorFactory(day, dayList, _workTimeCfg).create().calc(),
        '忘打卡'  : day.forgotPunch(),
        '加班'    : new OvertimeWorkCalculator(date, punchOut).calc(),
        '餐补'    : new MealStipendCalculator(date, punchOut).calc(),
        '试用应计': probaHour,
        '试用实计': isProba ? wageHour : 0,
        '转正应计': fullHour,
        '转正实计': !isProba ? wageHour : 0,
    ]

    return combineMaps([
        ['基本', _basicMap[employeeId]],
        ['明细', day.employee().toRawMap()],
        ['请假', leaveList.collectEntries { [it.leaveType(), it.leaveDuration()] }],
        ['计算', computeMap],
    ])
  }

  private static final DateTimeFormatter FORMAT = DateTimeFormatter.ofPattern('yyyy/MM/dd HH:mm')

  private final List<AttendPunchExcelV2Reader.Line> _punchList

  private final Map<String, List<AttendLeaveExcelReader.Line>> _leaveMap
  private final Map<String, Map> _basicMap

  private final List<WorkTimeSheetReader.Line> _workTimeCfg
  private final GlobalResultMaker.Result _globalCfg
}
